package com.github.kezhenxu94.acs.math;

import java.util.Arrays;
import java.util.Objects;

/**
 * Created by kezhenxu on Dec 19, 2016
 *
 * @author kezhenxu (kezhenxu94@163.com)
 */
public class Matrix {
	private double[][]	A;
	private int			m;
	private int			n;

	public static Matrix random(int m, int n) {
		double[][] A = Maths.random(m, n);
		Matrix matrix = new Matrix(A);
		return matrix;
	}

	public Matrix(double[][] A) {
		Objects.requireNonNull(A);

		this.A = A;
		this.m = A.length;
		this.n = A[0].length;
		for (int i = 0; i < A.length; i++)
			if (A[i].length != n)
				throw new IllegalArgumentException("All rows must have the same length.");
	}

	public Matrix(int m, int n, double fill) {
		this.m = m;
		this.n = n;
		this.A = new double[m][n];
		for (int i = 0; i < m; i++)
			Arrays.fill(A[i], fill);
	}

	public Matrix minus(Matrix other) {
		if (this.m != other.m || this.n != other.n)
			throw new IllegalArgumentException("Matrix must have the same rows and columns.");

		Matrix result = new Matrix(getArrayCopy());
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				result.set(i, j, this.A[i][j] - other.A[i][j]);
		return result;
	}

	public Matrix plus(Matrix other) {
		if (this.m != other.m || this.n != other.n)
			throw new IllegalArgumentException("Matrix must have the same rows and columns.");

		Matrix result = new Matrix(getArrayCopy());
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				result.set(i, j, this.A[i][j] + other.A[i][j]);
		return result;
	}

	public Matrix multiply(double scale) {
		Matrix result = new Matrix(getArrayCopy());
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				result.set(i, j, this.A[i][j] * scale);
		return result;
	}

	public Matrix multiply(Matrix other) {
		if (this.n != other.m)
			throw new IllegalArgumentException("Matrix inner dimensions must agree.");

		Matrix result = new Matrix(this.m, other.n, 0);
		for (int i = 0; i < this.m; i++) {
			for (int j = 0; j < other.n; j++) {
				double cij = 0;
				for (int k = 0; k < this.n; k++)
					cij += this.get(i, k) * other.get(k, j);
				result.set(i, j, cij);
			}
		}
		return result;
	}

	public Matrix transpose() {
		Matrix result = new Matrix(n, m, 0);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				result.set(i, j, A[j][i]);
		return result;
	}

	public double[][] getArray() {
		return A;
	}

	public double[][] getArrayCopy() {
		double[][] copy = new double[m][n];
		for (int i = 0; i < m; i++)
			System.arraycopy(A[i], 0, copy[i], 0, m);
		return copy;
	}

	public int getRowCount() {
		return m;
	}

	public int getColumnCount() {
		return n;
	}

	public double get(int row, int col) {
		return A[row][col];
	}

	public void set(int row, int col, double val) {
		A[row][col] = val;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[\n");
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				sb.append(A[i][j]).append(j < n - 1 ? ", " : "");
			}
			sb.append("\n");
		}
		sb.append("]");
		return sb.toString();
	}

}
